/*!
\file Graph.cpp
\brief Contains implementation of Graph.
\ingroup Graph
*/

#include"Graph.h" 

#include"Action.h"

#include<QByteArray>
#include<QMessageBox>
#include<QFile>

Graph::Graph( bool directed ){
	m_isDirected = directed;
}

Graph::~Graph(){

}

Node Graph::createNode( int identifier ){
	if( identifier < 0 )
		identifier = _getNewNodeIdentifier();
	
	if( !_registerNodeIdentifier( identifier ) ){
		identifier = _getNewNodeIdentifier();
		_registerNodeIdentifier(identifier);
		//TODO error
	}
	Node n(identifier);
	addNode(n);
	return n;
}

Node Graph::createNode( QString label, int identifier ){
	if( identifier < 0 )
		identifier = _getNewNodeIdentifier();
	
	if( !_registerNodeIdentifier( identifier ) ){
		identifier = _getNewNodeIdentifier();
		_registerNodeIdentifier(identifier);
		//TODO error
	}
	Node n(label, identifier);
	addNode(n);
	return n;
}

Edge Graph::createEdge( Node from, Node to, QString label, int identifier ){
	if( identifier < 0 )
		identifier = _getNewEdgeIdentifier();
	
	if( !_registerEdgeIdentifier(identifier) ){
		identifier = _getNewEdgeIdentifier();
		_registerEdgeIdentifier(identifier);
	}
	
	Edge e( from, to, label, identifier );
	addEdge(e);
	return e;
}



Graph *Graph::fromFile( const char *file ){ 
	printf("making new graph\n");
	Graph *g = new Graph; 
	printf("start reading\n");
	g->_fromFile(file);
	return g;
}

bool Graph::addEdge( Edge edge ){
	if( hasEdge(edge) )
		return false;
	m_edges.push_back( edge );
	
	report( new EdgeAddAction(edge.identifier(), edge.from().identifier(), edge.to().identifier(), edge.label(), true));
	report( new CommentAction( edge.toString().append( " is temporarily highlighted as it is newly added." ) ) );
	report( new EdgeHighlightAction(edge.identifier()) );
	report( new EdgeUnHighlightAction(edge.identifier()) );
	
	return true;
}

bool Graph::addNode( Node node ){
	if( hasNode( node ) )
		return false;
	m_nodes.push_back( node );
	
	report( new NodeAddAction( node.identifier(), node.label(), true ) );
	report( new CommentAction( node.toString().append(" is temporarily highlighted as it is newly added.") ) );
	report( new NodeHighlightAction(node.identifier()) );
	report( new NodeUnHighlightAction(node.identifier()) );
	
	return true;
}

void Graph::removeEdge( const Edge &edge ){
	if( m_edges.removeOne(edge) ){
		report( new CommentAction( edge.toString().append(" is being highlighted as it will be deleted.") ) );
		report( new EdgeHighlightAction(edge.identifier()) );
		report( new EdgeRemoveAction(edge.identifier(), edge.from().identifier(), edge.to().identifier()) );
	}
}

void Graph::removeNode( const Node &node ){
	if( m_nodes.removeOne(node) ){
		report( new CommentAction( node.toString().append( " is being highlighted as it will be deleted." ) ) );
		report( new NodeHighlightAction(node.identifier()) );
		report( new NodeRemoveAction(node.identifier()) );
	}
}

bool Graph::hasEdge( const Edge &edge )const{
	for( int i = 0; i < m_edges.size(); i++ )
		if( edge == m_edges[i] )
			return true;
	return false;
}

bool Graph::hasNode( const Node &node )const{
	for( int i = 0; i < m_nodes.size(); i++ )
		if( node == m_nodes[i] )
			return true;
	return false;
}

QList<Node> Graph::neighboursOf( const Node &node ){
	QList<Node> ret;
	
	for( unsigned int i = 0; i < numEdges(); i++ ){
		if( m_edges[i].from() == node )
			ret.push_back( m_edges[i].to() );
		else if( m_edges[i].to() == node )
			ret.push_back( m_edges[i].to() );
	}
	
	return ret;//WARNING: May contain doubles
}

QList<Node> Graph::reachableFrom( const Node &node ){
	QList<Node> ret;
	for( unsigned int i = 0; i < numEdges(); i++ ){
		const Edge &e = edge(i);
		if( e.from() == node )
			ret.push_back( e.to() );
		else if( !m_isDirected && e.to() == node )
			ret.push_back( e.from() );
	}
	return ret;
}

QList<Edge> Graph::edgesFrom( const Node &node ){
	QList<Edge> ret;
	for( unsigned int i = 0; i < numEdges(); i++ ){
		if( edge(i).from() == node || ( !m_isDirected && edge(i).to() == node ) )
			ret.push_back( edge(i) );
	}
	return ret;
}

QStringList Graph::toDOT()const{
	QStringList result;
	if( m_isDirected )
		result.push_back( "digraph{" );
	else
		result.push_back( "graph{" );
	
	_nodesToDOT( result );
	_edgesToDOT( result );
	
	result.push_back( "}" );
	return result;
}

void Graph::_nodesToDOT( QStringList &result )const{
	result.push_back( "/*nodes*/" );
	for( unsigned int i = 0; i < numNodes(); i++ )
		result.push_back( m_nodes[i]._toDOT() );
}

void Graph::_edgesToDOT( QStringList &result )const{
	result.push_back( "/*edges*/" );
	for( unsigned int i = 0; i < numEdges(); i++ )
		result.push_back( m_edges[i]._toDOT(m_isDirected) );
}


void Graph::report( Action *action )const{
	if ( receivers(SIGNAL(reportAction(Action*))) > 0 ) {
		emit reportAction(action);
	}else{
		delete action;
	}
}

Node Graph::node( int i )const{
	report( new NodeAccessAction( m_nodes[i].identifier() ) );
	return m_nodes[i];
}

Node Graph::nodeWithId(int i, bool *ok)const{
	if( ok )
		*ok = true;
	for( int j = 0; j < m_nodes.size(); j++ ){
		if( m_nodes[j].identifier() == i ){
			report( new NodeAccessAction(i) );
			return m_nodes[j];
		}
	}
	if(ok)
		*ok = false;
	return m_nodes[0];
}

Edge Graph::edge( int i )const{
	report( new EdgeAccessAction( m_edges[i].identifier(), m_edges[i].from().identifier(), m_edges[i].from().identifier(), m_edges[i].label() ) );
	return m_edges[i];
}

Edge Graph::edgeWithId(int i, bool *ok)const{
	if( ok )
		*ok = true;
	for( int j = 0; j < m_edges.size(); j++ ){
		if( m_edges[j].identifier() == i ){
			Edge e = m_edges[j];
			report( new EdgeAccessAction(e.from().identifier(), e.to().identifier(), e.identifier(), e.label()) );
			return e;
		}
	}
	
	if( ok )
		*ok = false;
	
	return m_edges[0];
}

EdgeList Graph::operator[](int i)const{
	EdgeList ret( m_isDirected );
	for( int j = 0; j < m_edges.size(); j++ ){
		const Edge &e = m_edges[j];
		if( e.from().identifier() == i || (!m_isDirected && e.to().identifier() == i) )
			ret.add(e);
	}
	report( new EdgeAccessAction( -1, -1, -1, QString() ) );
	return ret;
}

void Graph::_fromFile( const char *file ){
	QFile f(file);
	if( !f.open(QIODevice::ReadOnly) ){
		QMessageBox::warning( 0, "Read error", QString("Could not open file: ") + QString(file) );
		return;
	}
	QByteArray data;
	while( true ){
		data = f.readLine();
		if( data.isNull() || data.isEmpty() )
			break;
		QString text(data);
		if(text == "\n" || text.trimmed().startsWith("//"))
			continue;
		else if(text.trimmed().startsWith("directed")){
			text = text.remove("directed").trimmed().remove("=").trimmed();
			if( text == "false" || text == "0" )
				m_isDirected = false;
			else
				m_isDirected = true;
		}else if( text.trimmed().startsWith("Node") ){
			text = text.trimmed().remove("Node").trimmed();
			text = text.right(text.size()-1).trimmed();
			text = text.left(text.size()-1).trimmed();
			QStringList params = text.split(",");
			if( params.size() == 0 )
				continue;//Error
			int id = params[0].toInt();
			params.pop_front();
			QString label = params.size()?params.join(","):QString("");
			createNode(label, id);
		}else if( text.trimmed().startsWith("Edge") ){
			text = text.trimmed().remove("Edge").trimmed();
			
			if( !text.endsWith(")") ){
				printf( "Invalid edge data: %s\n", text.toAscii().data() );
			}
			
			text = text.right(text.size()-1).trimmed();
			text = text.left(text.size()-1).trimmed();
			
			QStringList params = text.split(",");
			if( params.size() < 2 )
				continue;//Error
			int from = params[0].toInt();
			params.pop_front();
			int to = params[0].toInt();
			params.pop_front();
			
			QString label = params.size()?params.join(","):"";
			label = label.replace("\"", "");
// 			printf("Edge label added: %s\n", label.toAscii().data());
			createEdge(nodeWithId(from), nodeWithId(to), label);
		}
	}
}

int Graph::_getNewEdgeIdentifier(){
	qSort(m_edgeIdentifiers);

	if( m_edgeIdentifiers.size() == 0 )
		return 0;
	
	return m_edgeIdentifiers.back()+1;
}

bool Graph::_registerEdgeIdentifier( int identifier ){
	for( int i = 0; i < m_edgeIdentifiers.size(); i++ )
		if( m_edgeIdentifiers[i] == identifier )
			return false;
	
	m_edgeIdentifiers.push_back( identifier );
	return true;
}

int Graph::_getNewNodeIdentifier(){
	qSort(m_nodeIdentifiers);

	if( m_nodeIdentifiers.size() == 0 )
		return 0;
	
	return m_nodeIdentifiers.back()+1;
}

bool Graph::_registerNodeIdentifier( int identifier ){
	for( int i = 0; i < m_nodeIdentifiers.size(); i++ )
		if( m_nodeIdentifiers[i] == identifier )
			return false;
	
	m_nodeIdentifiers.push_back( identifier );
	return true;
}